package com.carpedil.ms.controller;

import com.carpedil.ms.entity.Order;
import com.carpedil.ms.entity.Stock;
import com.carpedil.ms.mapper.StockMapper;
import com.carpedil.ms.service.OrderService;
import com.carpedil.ms.service.UserService;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;

/** @author Administrator */
@RestController
@RequestMapping("stock")
@Slf4j
public class StockController {
  @Autowired private OrderService orderService;
  @Autowired private StockMapper stockMapper;
  @Autowired private StringRedisTemplate redisTemplate;
  @Autowired private UserService userService;
  /** 创建令牌桶实例 设置初始令牌数:10个 */
  private RateLimiter rateLimiter = RateLimiter.create(20);

  /** 预先将秒杀商品的id以 'kill'+id 的形式 放入redis,并设置过期时间 当处理用户请求的时候,需求到redis中验证商品是否过期 */
  @PostConstruct
  private void initRedis() {
    // 获取秒杀商品集合
    List<Stock> list = stockMapper.list();
    // 将要参加秒杀活动的商品id数据放入redis  格式: key: kill1(kill拼接商品id)  value: 1(商品id) 过期时间:30分钟
    for (Stock stock : list) {
      if (!redisTemplate.hasKey("kill" + stock.getId())) {
        redisTemplate
            .opsForValue()
            .set("kill" + stock.getId(), stock.getId() + "", 3600 * 12, TimeUnit.SECONDS);
      }
    }
  }

  @GetMapping("list")
  public List<Stock> list() {
    return stockMapper.list();
  }

  @GetMapping("test")
  public String testRateLimiter(Integer id) {
    // 1. 如果请求没有获得令牌,则一直等待直到拿到令牌 rateLimiter.acquire():获取令牌的时间
    // log.info("等待时间:" + rateLimiter.acquire());

    // 2. 可以设置一个等待时间,如果在等待时间里获取到令牌token,则放行请求处理业务,如果没有拿到令牌,则直接抛弃该请求
    if (!rateLimiter.tryAcquire(2, TimeUnit.SECONDS)) {
      System.out.println("当前请求没有拿到令牌,被限流,无法调用后续业务接口");
      return "失败";
    }
    System.out.println("处理业务..............");
    return "成功!!!";
  }

  @GetMapping("md5")
  public String getMd5(String username, String password, Integer id) {
    String md5;
    try {
      md5 = orderService.getMd5(username, password, id);
    } catch (Exception e) {
      e.printStackTrace();
      return "md5获取失败" + e.getMessage();
    }
    return md5;
  }

  /**
   * 定义一个秒杀的接口 乐观锁防止超卖 +md5接口隐藏
   *
   * @return 如果秒杀成功,则返回订单id
   */
  @GetMapping("killTokenmd5limit")
  public String stockKillTokenLimit(Integer id, String username, String md5) {
    if (!rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
      return "抢购失败, 活动过于火爆,服务器爆满,请重试!!!";
    }
    try {
      // 单用户访问调用接口的频次校验限制
      int userCount = userService.saveUserCount(username);
      log.info("用户访问频次:[{}]", userCount);
      boolean isBanned = userService.getUserCount(username);
      if (isBanned) {
        return "抢购失败,超过访问频次!!!";
      }
      // 判断当前用户是否已经秒杀过产品  一人限一单
      List<Order> list = orderService.checkOrderByUserName(username);
      if (list.size() > 0) {
        return "抱歉,本次活动,仅限一人一单,数量有限";
      }
      // 调用秒杀业务
      return "恭喜你, 秒杀成功,订单编号为:" + orderService.kill(id, username, md5);
    } catch (Exception e) {
      e.printStackTrace();
      return e.getMessage();
    }
  }
}
